/*-------------------------------------------------------------------------
 *
 * rewriteDefine.c
 *	  定义重写规则的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/rewrite/rewriteDefine.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_rewrite.h"
#include "catalog/storage.h"
#include "commands/policy.h"
#include "commands/tablecmds.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_utilcmd.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rewriteSupport.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"


static void fc_checkRuleResultList(List *fc_targetList, TupleDesc fc_resultDesc,
								bool fc_isSelect, bool fc_requireColumnNameMatch);
static bool fc_setRuleCheckAsUser_walker(Node *fc_node, Oid *fc_context);
static void fc_setRuleCheckAsUser_Query(Query *fc_qry, Oid fc_userid);


/*
 * InsertRule -
 *	  获取参数并将其作为一行插入系统关系“pg_rewrite”
 */
static Oid fc_InsertRule(const char *fc_rulname,
		   int fc_evtype,
		   Oid fc_eventrel_oid,
		   bool fc_evinstead,
		   Node *fc_event_qual,
		   List *fc_action,
		   bool fc_replace)
{
	char	   *fc_evqual = nodeToString(fc_event_qual);
	char	   *fc_actiontree = nodeToString((Node *) fc_action);
	Datum		fc_values[Natts_pg_rewrite];
	bool		fc_nulls[Natts_pg_rewrite];
	bool		fc_replaces[Natts_pg_rewrite];
	NameData	fc_rname;
	Relation	pg_rewrite_desc;
	HeapTuple	fc_tup,
				fc_oldtup;
	Oid			fc_rewriteObjectId;
	ObjectAddress fc_myself,
				fc_referenced;
	bool		fc_is_update = false;

	/*
	 * 设置*nulls和*values数组
	 */
	MemSet(fc_nulls, false, sizeof(fc_nulls));

	namestrcpy(&fc_rname, fc_rulname);
	fc_values[Anum_pg_rewrite_rulename - 1] = NameGetDatum(&fc_rname);
	fc_values[Anum_pg_rewrite_ev_class - 1] = ObjectIdGetDatum(fc_eventrel_oid);
	fc_values[Anum_pg_rewrite_ev_type - 1] = CharGetDatum(fc_evtype + '0');
	fc_values[Anum_pg_rewrite_ev_enabled - 1] = CharGetDatum(RULE_FIRES_ON_ORIGIN);
	fc_values[Anum_pg_rewrite_is_instead - 1] = BoolGetDatum(fc_evinstead);
	fc_values[Anum_pg_rewrite_ev_qual - 1] = CStringGetTextDatum(fc_evqual);
	fc_values[Anum_pg_rewrite_ev_action - 1] = CStringGetTextDatum(fc_actiontree);

	/*
	 * 准备存储新的pg_rewrite元组
	 */
	pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);

	/*
	 * 检查是否正在替换现有元组
	 */
	fc_oldtup = SearchSysCache2(RULERELNAME,
							 ObjectIdGetDatum(fc_eventrel_oid),
							 PointerGetDatum(fc_rulname));

	if (HeapTupleIsValid(fc_oldtup))
	{
		if (!fc_replace)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("rule \"%s\" for relation \"%s\" already exists",
							fc_rulname, get_rel_name(fc_eventrel_oid))));

		/*
		 * 在替换时，我们不需要替换每个属性
		 */
		MemSet(fc_replaces, false, sizeof(fc_replaces));
		fc_replaces[Anum_pg_rewrite_ev_type - 1] = true;
		fc_replaces[Anum_pg_rewrite_is_instead - 1] = true;
		fc_replaces[Anum_pg_rewrite_ev_qual - 1] = true;
		fc_replaces[Anum_pg_rewrite_ev_action - 1] = true;

		fc_tup = heap_modify_tuple(fc_oldtup, RelationGetDescr(pg_rewrite_desc),
								fc_values, fc_nulls, fc_replaces);

		CatalogTupleUpdate(pg_rewrite_desc, &fc_tup->t_self, fc_tup);

		ReleaseSysCache(fc_oldtup);

		fc_rewriteObjectId = ((Form_pg_rewrite) GETSTRUCT(fc_tup))->oid;
		fc_is_update = true;
	}
	else
	{
		fc_rewriteObjectId = GetNewOidWithIndex(pg_rewrite_desc,
											 RewriteOidIndexId,
											 Anum_pg_rewrite_oid);
		fc_values[Anum_pg_rewrite_oid - 1] = ObjectIdGetDatum(fc_rewriteObjectId);

		fc_tup = heap_form_tuple(pg_rewrite_desc->rd_att, fc_values, fc_nulls);

		CatalogTupleInsert(pg_rewrite_desc, fc_tup);
	}


	heap_freetuple(fc_tup);

	/* 如果替换，去掉旧依赖并建立新依赖 */
	if (fc_is_update)
		deleteDependencyRecordsFor(RewriteRelationId, fc_rewriteObjectId, false);

	/*
	 * 安装对规则关系的依赖，以确保在关系删除时它会消失。
	 * 如果规则是在SELECT上，将依赖设置为隐式 --- 
	 * 这防止删除视图的SELECT规则。其他类型的规则可以是AUTO。
	 */
	fc_myself.classId = RewriteRelationId;
	fc_myself.objectId = fc_rewriteObjectId;
	fc_myself.objectSubId = 0;

	fc_referenced.classId = RelationRelationId;
	fc_referenced.objectId = fc_eventrel_oid;
	fc_referenced.objectSubId = 0;

	recordDependencyOn(&fc_myself, &fc_referenced,
					   (fc_evtype == CMD_SELECT) ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);

	/*
	 * 还安装在动作和条件中引用的对象的依赖。
	 */
	recordDependencyOnExpr(&fc_myself, (Node *) fc_action, NIL,
						   DEPENDENCY_NORMAL);

	if (fc_event_qual != NULL)
	{
		/* 查找包含OLD/NEW rtable条目的查询 */
		Query	   *fc_qry = linitial_node(Query, fc_action);

		fc_qry = getInsertSelectQuery(fc_qry, NULL);
		recordDependencyOnExpr(&fc_myself, fc_event_qual, fc_qry->rtable,
							   DEPENDENCY_NORMAL);
	}

	/* 新规则的后创建钩子 */
	InvokeObjectPostCreateHook(RewriteRelationId, fc_rewriteObjectId, 0);

	table_close(pg_rewrite_desc, RowExclusiveLock);

	return fc_rewriteObjectId;
}

/*
 * DefineRule
 *		执行CREATE RULE命令。
 */
ObjectAddress DefineRule(RuleStmt *fc_stmt, const char *fc_queryString)
{
	List	   *fc_actions;
	Node	   *fc_whereClause;
	Oid			fc_relId;

	/* 解析分析。 */
	transformRuleStmt(fc_stmt, fc_queryString, &fc_actions, &fc_whereClause);

	/*
	 * 查找并锁定关系。锁定级别应与
	 * DefineQueryRewrite相匹配。
	 */
	fc_relId = RangeVarGetRelid(fc_stmt->relation, AccessExclusiveLock, false);

	/* ...并执行 */
	return DefineQueryRewrite(fc_stmt->rulename,
							  fc_relId,
							  fc_whereClause,
							  fc_stmt->event,
							  fc_stmt->instead,
							  fc_stmt->replace,
							  fc_actions);
}


/*
 * DefineQueryRewrite
 *		创建一个规则
 *
 * 本质上与 DefineRule() 相同，只是规则的
 * 动作和条件已经通过解析分析。
 */
ObjectAddress DefineQueryRewrite(const char *fc_rulename,
				   Oid fc_event_relid,
				   Node *fc_event_qual,
				   CmdType fc_event_type,
				   bool fc_is_instead,
				   bool fc_replace,
				   List *fc_action)
{
	Relation	fc_event_relation;
	ListCell   *fc_l;
	Query	   *fc_query;
	bool		fc_RelisBecomingView = false;
	Oid			fc_ruleId = InvalidOid;
	ObjectAddress fc_address;

	/*
	 * 如果我们正在安装一个 ON SELECT 规则，我们最好获得
	 * AccessExclusiveLock，以确保当前没有 SELECT 正在事件关系上运行。
	 * 对于其他类型的规则，获取 ShareRowExclusiveLock 足以锁定插入/更新/删除操作，并
	 * 确保我们锁定当前的 CREATE RULE 语句；但由于访问目录条目的竞争条件，
	 * 我们现在还不能这样做。
	 *
	 * 请注意，这个锁级别应与 DefineRule 中使用的级别匹配。
	 */
	fc_event_relation = table_open(fc_event_relid, AccessExclusiveLock);

	/*
	 * 验证关系的类型是否可以合理地应用规则。
	 * 内部调用者可以针对物化视图，但 transformRuleStmt()
	 * 会阻止他们对此操作。不要在错误信息中提到它们。
	 */
	if (fc_event_relation->rd_rel->relkind != RELKIND_RELATION &&
		fc_event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
		fc_event_relation->rd_rel->relkind != RELKIND_VIEW &&
		fc_event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("relation \"%s\" cannot have rules",
						RelationGetRelationName(fc_event_relation)),
				 errdetail_relkind_not_supported(fc_event_relation->rd_rel->relkind)));

	if (!allowSystemTableMods && IsSystemRelation(fc_event_relation))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						RelationGetRelationName(fc_event_relation))));

	/*
	 * 检查用户是否有权限将规则应用于此关系。
	 */
	if (!pg_class_ownercheck(fc_event_relid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(fc_event_relation->rd_rel->relkind),
					   RelationGetRelationName(fc_event_relation));

	/*
	 * 没有修改 OLD 或 NEW 的规则动作
	 */
	foreach(fc_l, fc_action)
	{
		fc_query = lfirst_node(Query, fc_l);
		if (fc_query->resultRelation == 0)
			continue;
		/* 不要被 INSERT/SELECT 迷惑 */
		if (fc_query != getInsertSelectQuery(fc_query, NULL))
			continue;
		if (fc_query->resultRelation == PRS2_OLD_VARNO)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("rule actions on OLD are not implemented"),
					 errhint("Use views or triggers instead.")));
		if (fc_query->resultRelation == PRS2_NEW_VARNO)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("rule actions on NEW are not implemented"),
					 errhint("Use triggers instead.")));
	}

	if (fc_event_type == CMD_SELECT)
	{
		/*
		 * ON SELECT 的规则限制为视图定义
		 *
		 * 所以不能有 INSTEAD NOTHING, ...
		 */
		if (list_length(fc_action) == 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("INSTEAD NOTHING rules on SELECT are not implemented"),
					 errhint("Use views instead.")));

		/*
		 * ... 不能有多个动作，...
		 */
		if (list_length(fc_action) > 1)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("multiple actions for rules on SELECT are not implemented")));

		/*
		 * ... 唯一的动作必须是 SELECT，...
		 */
		fc_query = linitial_node(Query, fc_action);
		if (!fc_is_instead ||
			fc_query->commandType != CMD_SELECT)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("rules on SELECT must have action INSTEAD SELECT")));

		/*
		 * ... 它不能包含数据修改的 WITH ...
		 */
		if (fc_query->hasModifyingCTE)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("rules on SELECT must not contain data-modifying statements in WITH")));

		/*
		 * ... 不能有规则条件，...
		 */
		if (fc_event_qual != NULL)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("event qualifications are not implemented for rules on SELECT")));

		/*
		 * ... SELECT 动作的目标列表必须完全匹配
		 * 事件关系，...
		 */
		fc_checkRuleResultList(fc_query->targetList,
							RelationGetDescr(fc_event_relation),
							true,
							fc_event_relation->rd_rel->relkind !=
							RELKIND_MATVIEW);

		/*
		 * ... 不能已经有另一个 ON SELECT 规则 ...
		 */
		if (!fc_replace && fc_event_relation->rd_rules != NULL)
		{
			int			fc_i;

			for (fc_i = 0; fc_i < fc_event_relation->rd_rules->numLocks; fc_i++)
			{
				RewriteRule *fc_rule;

				fc_rule = fc_event_relation->rd_rules->rules[fc_i];
				if (fc_rule->event == CMD_SELECT)
					ereport(ERROR,
							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
							 errmsg("\"%s\" is already a view",
									RelationGetRelationName(fc_event_relation))));
			}
		}

		/*
		 * ... 最后规则必须命名为 _RETURN。
		 */
		if (strcmp(fc_rulename, ViewSelectRuleName) != 0)
		{
			/*
			 * 在 7.3 之前的版本中，预期名称是 _RETviewname。为了
			 * 与旧 pg_dump 输出的向后兼容，接受它
			 * 并默默地将其更改为 _RETURN。由于这只是一个快速的
			 * 向后兼容性解决方案，限制检查的字符数
			 * 到 NAMEDATALEN 的几个字符；这样可以避免
			 * 担心多字节字符可能被截断的位置。
			 */
			if (strncmp(fc_rulename, "_RET", 4) != 0 ||
				strncmp(fc_rulename + 4, RelationGetRelationName(fc_event_relation),
						NAMEDATALEN - 4 - 4) != 0)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("view rule for \"%s\" must be named \"%s\"",
								RelationGetRelationName(fc_event_relation),
								ViewSelectRuleName)));
			fc_rulename = pstrdup(ViewSelectRuleName);
		}

		/*
		 * 我们是在将一个关系转换为视图吗？
		 *
		 * 如果是，请检查关系是否为空，因为该关系的存储
		 * 将被删除。同时坚持认为该关系不能参与分区，也不能
		 * 有任何触发器、索引、子表或父表、RLS 策略或启用 RLS。
		 * （注意：一些测试过于严格，因为它们会拒绝曾经
		 * 有过但现在没有的关系。但我们并不在乎，因为
		 * 这种将关系转换为视图的整个业务已经是一个过时的
		 * 垃圾解决方案，用于允许参与循环
		 * 依赖的视图的转储/重新加载。）
		 *
		 * 还要确保该关系不在我们自己会话的任何外部 SQL
		 * 命令中被操作。
		 */
		if (fc_event_relation->rd_rel->relkind != RELKIND_VIEW &&
			fc_event_relation->rd_rel->relkind != RELKIND_MATVIEW)
		{
			TableScanDesc fc_scanDesc;
			Snapshot	fc_snapshot;
			TupleTableSlot *fc_slot;

			CheckTableNotInUse(fc_event_relation, "CREATE RULE");

			if (fc_event_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("cannot convert partitioned table \"%s\" to a view",
								RelationGetRelationName(fc_event_relation))));

			/* 唯一剩下的情况： */
			Assert(fc_event_relation->rd_rel->relkind == RELKIND_RELATION);

			if (fc_event_relation->rd_rel->relispartition)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("cannot convert partition \"%s\" to a view",
								RelationGetRelationName(fc_event_relation))));

			fc_snapshot = RegisterSnapshot(GetLatestSnapshot());
			fc_scanDesc = table_beginscan(fc_event_relation, fc_snapshot, 0, NULL);
			fc_slot = table_slot_create(fc_event_relation, NULL);
			if (table_scan_getnextslot(fc_scanDesc, ForwardScanDirection, fc_slot))
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("could not convert table \"%s\" to a view because it is not empty",
								RelationGetRelationName(fc_event_relation))));
			ExecDropSingleTupleTableSlot(fc_slot);
			table_endscan(fc_scanDesc);
			UnregisterSnapshot(fc_snapshot);

			if (fc_event_relation->rd_rel->relhastriggers)
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("could not convert table \"%s\" to a view because it has triggers",
								RelationGetRelationName(fc_event_relation)),
						 errhint("In particular, the table cannot be involved in any foreign key relationships.")));

			if (fc_event_relation->rd_rel->relhasindex)
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("could not convert table \"%s\" to a view because it has indexes",
								RelationGetRelationName(fc_event_relation))));

			if (fc_event_relation->rd_rel->relhassubclass)
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("could not convert table \"%s\" to a view because it has child tables",
								RelationGetRelationName(fc_event_relation))));

			if (has_superclass(RelationGetRelid(fc_event_relation)))
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("could not convert table \"%s\" to a view because it has parent tables",
								RelationGetRelationName(fc_event_relation))));

			if (fc_event_relation->rd_rel->relrowsecurity)
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("could not convert table \"%s\" to a view because it has row security enabled",
								RelationGetRelationName(fc_event_relation))));

			if (relation_has_policies(fc_event_relation))
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("could not convert table \"%s\" to a view because it has row security policies",
								RelationGetRelationName(fc_event_relation))));

			fc_RelisBecomingView = true;
		}
	}
	else
	{
		/*
		 * 对于非 SELECT 规则，RETURNING 列表最多只能出现在一个
		 * 动作中......在条件规则或非 INSTEAD 规则中不能有任何
		 * RETURNING 列表。（实际上，在同一事件的所有规则中，最多
		 * 只能有一个 RETURNING 列表，但最好在规则扩展时强制
		 * 这一点。）如果有 RETURNING 列表，它必须匹配事件关系。
		 */
		bool		fc_haveReturning = false;

		foreach(fc_l, fc_action)
		{
			fc_query = lfirst_node(Query, fc_l);

			if (!fc_query->returningList)
				continue;
			if (fc_haveReturning)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot have multiple RETURNING lists in a rule")));
			fc_haveReturning = true;
			if (fc_event_qual != NULL)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("RETURNING lists are not supported in conditional rules")));
			if (!fc_is_instead)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("RETURNING lists are not supported in non-INSTEAD rules")));
			fc_checkRuleResultList(fc_query->returningList,
								RelationGetDescr(fc_event_relation),
								false, false);
		}

		/*
		 * 最后，如果不是 ON SELECT 规则，则必须 *不*
		 * 命名为 _RETURN。这可以防止意外或恶意地将
		 * 视图的 ON SELECT 规则替换为其他类型的规则。
		 */
		if (strcmp(fc_rulename, ViewSelectRuleName) == 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("non-view rule for \"%s\" must not be named \"%s\"",
							RelationGetRelationName(fc_event_relation),
							ViewSelectRuleName)));
	}

	/*
	 * 该规则是允许的 - 准备安装它。
	 */

	/* 如果动作为null且不是INSTEAD，则丢弃规则；它是一个无操作 */
	if (fc_action != NIL || fc_is_instead)
	{
		fc_ruleId = fc_InsertRule(fc_rulename,
							fc_event_type,
							fc_event_relid,
							fc_is_instead,
							fc_event_qual,
							fc_action,
							fc_replace);

		/*
		 * 设置 pg_class 的 'relhasrules' 字段为真，针对事件关系。
		 *
		 * 重要的副作用：广播一个 SI 通知，以强制所有
		 * 后端（包括我！）更新 relcache 条目以使用新规则。
		 */
		SetRelationRuleStatus(fc_event_relid, true);
	}

	/* ---------------------------------------------------------------------
	 * 如果关系将变成一个视图：
	 * - 删除关联的存储文件
	 * - 消除 pg_attribute 中的任何系统属性；视图不应该
	 *	 有任何这些
	 * - 移除 toast 表；不再需要它，其
	 *	 存在会使清理稍微复杂一些
	 * - 将 relkind 设置为 RELKIND_VIEW，并调整其他 pg_class 字段
	 *	 以适合视图
	 *
	 * 注意：我们最好获得 AccessExclusiveLock 来执行此操作...
	 * ---------------------------------------------------------------------
	 */
	if (fc_RelisBecomingView)
	{
		Relation	fc_relationRelation;
		Oid			fc_toastrelid;
		HeapTuple	fc_classTup;
		Form_pg_class fc_classForm;

		fc_relationRelation = table_open(RelationRelationId, RowExclusiveLock);
		fc_toastrelid = fc_event_relation->rd_rel->reltoastrelid;

		/* 在表仍看起来像一个表时删除存储 */
		RelationDropStorage(fc_event_relation);
		DeleteSystemAttributeTuples(fc_event_relid);

		/*
		 * 删除 toast 表（如果有的话）。（这不会处理更新的
		 * 表关系中 pg_class 条目的 toast 字段；我们在下面处理。）
		 */
		if (OidIsValid(fc_toastrelid))
		{
			ObjectAddress fc_toastobject;

			
/*
			 * 删除 toast 关系对主关系的依赖，以便我们可以在不删除后者的情况下删除前者。
			 */
			deleteDependencyRecordsFor(RelationRelationId, fc_toastrelid,
									   false);

			/* 使依赖记录的删除可见 */
			CommandCounterIncrement();

			/* 现在删除 toast 表，包括其索引 */
			fc_toastobject.classId = RelationRelationId;
			fc_toastobject.objectId = fc_toastrelid;
			fc_toastobject.objectSubId = 0;
			performDeletion(&fc_toastobject, DROP_RESTRICT,
							PERFORM_DELETION_INTERNAL);
		}

		/*
		 * SetRelationRuleStatus 可能已经更新了 pg_class 行，因此我们必须
		 * 在再次尝试更新之前提前递增命令计数器。
		 */
		CommandCounterIncrement();

		/*
		 * 修复 pg_class 条目，使其看起来像正常视图的条目，包括设置
		 * 正确的 relkind 以及移除我们可能在上面删除的 toast 表的 reltoastrelid。
		 */
		fc_classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(fc_event_relid));
		if (!HeapTupleIsValid(fc_classTup))
			elog(ERROR, "cache lookup failed for relation %u", fc_event_relid);
		fc_classForm = (Form_pg_class) GETSTRUCT(fc_classTup);

		fc_classForm->relam = InvalidOid;
		fc_classForm->reltablespace = InvalidOid;
		fc_classForm->relpages = 0;
		fc_classForm->reltuples = -1;
		fc_classForm->relallvisible = 0;
		fc_classForm->reltoastrelid = InvalidOid;
		fc_classForm->relhasindex = false;
		fc_classForm->relkind = RELKIND_VIEW;
		fc_classForm->relfrozenxid = InvalidTransactionId;
		fc_classForm->relminmxid = InvalidMultiXactId;
		fc_classForm->relreplident = REPLICA_IDENTITY_NOTHING;

		CatalogTupleUpdate(fc_relationRelation, &fc_classTup->t_self, fc_classTup);

		heap_freetuple(fc_classTup);
		table_close(fc_relationRelation, RowExclusiveLock);
	}

	ObjectAddressSet(fc_address, RewriteRelationId, fc_ruleId);

	/* 关闭关系，但在提交之前保持锁... */
	table_close(fc_event_relation, NoLock);

	return fc_address;
}

/*
 * checkRuleResultList
 *		验证目标列表产生与tupledesc兼容的输出
 *
 * 目标列表可以是SELECT目标列表或RETURNING列表；
 * isSelect用于区分这两者。 这用于选择错误消息。
 *
 * SELECT目标列表可以选择性地要求列名匹配。
 */
static void fc_checkRuleResultList(List *fc_targetList, TupleDesc fc_resultDesc, bool fc_isSelect,
					bool fc_requireColumnNameMatch)
{
	ListCell   *fc_tllist;
	int			fc_i;

	/* 只有SELECT可能要求列名匹配。 */
	Assert(fc_isSelect || !fc_requireColumnNameMatch);

	fc_i = 0;
	foreach(fc_tllist, fc_targetList)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_tllist);
		Oid			fc_tletypid;
		int32		fc_tletypmod;
		Form_pg_attribute fc_attr;
		char	   *fc_attname;

		/* resjunk条目可以被忽略。 */
		if (fc_tle->resjunk)
			continue;
		fc_i++;
		if (fc_i > fc_resultDesc->natts)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 fc_isSelect ?
					 errmsg("SELECT rule's target list has too many entries") :
					 errmsg("RETURNING list has too many entries")));

		fc_attr = TupleDescAttr(fc_resultDesc, fc_i - 1);
		fc_attname = NameStr(fc_attr->attname);

		/*
		 * 不允许在关系中删除列。 这在创建ON SELECT规则时并不真的是
		 * 预期的情况。如果有人试图将一个删除了列的关系转换为视图，
		 * 这是可能的，但我们关心支持表到视图转换的唯一案例是pg_dump，
		 * 而pg_dump不会这样做。
		 *
		 * 不幸的是，添加带有RETURNING的规则到常规表时也可能出现这种情况，
		 * 拒绝这种情况则更加麻烦。在原则上，我们可以通过修改目标列表支持该情况，
		 * 包括对应于删除列的虚拟NULL列。然而，
		 * ruleutils.c等地方必须修复以不处理这样的条目，
		 * 这将需要不确定且可能相当大的工作量。 （注意，我们不能通过标记虚拟
		 * 列为resjunk来躲避这一点，因为恰恰是非resjunk tlist列
		 * 被期望对应于表列。）
		 */
		if (fc_attr->attisdropped)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 fc_isSelect ?
					 errmsg("cannot convert relation containing dropped columns to view") :
					 errmsg("cannot create a RETURNING list for a relation containing dropped columns")));

		/* 如果需要，检查名称匹配；这里无需两个错误文本 */
		if (fc_requireColumnNameMatch && strcmp(fc_tle->resname, fc_attname) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("SELECT rule's target entry %d has different column name from column \"%s\"",
							fc_i, fc_attname),
					 errdetail("SELECT target entry is named \"%s\".",
							   fc_tle->resname)));

		/* 检查类型匹配。 */
		fc_tletypid = exprType((Node *) fc_tle->expr);
		if (fc_attr->atttypid != fc_tletypid)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 fc_isSelect ?
					 errmsg("SELECT rule's target entry %d has different type from column \"%s\"",
							fc_i, fc_attname) :
					 errmsg("RETURNING list's entry %d has different type from column \"%s\"",
							fc_i, fc_attname),
					 fc_isSelect ?
					 errdetail("SELECT target entry has type %s, but column has type %s.",
							   format_type_be(fc_tletypid),
							   format_type_be(fc_attr->atttypid)) :
					 errdetail("RETURNING list entry has type %s, but column has type %s.",
							   format_type_be(fc_tletypid),
							   format_type_be(fc_attr->atttypid))));

		/*
		 * 只有当其中一个类型修饰符为-1，即，
		 * “未指定”时，才允许类型修饰符不同。 这在“numeric”等情况下是必要的，
		 * 因为表将有一个填写的默认长度，但选择
		 * 规则的表达式可能会有typmod = -1。
		 */
		fc_tletypmod = exprTypmod((Node *) fc_tle->expr);
		if (fc_attr->atttypmod != fc_tletypmod &&
			fc_attr->atttypmod != -1 && fc_tletypmod != -1)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 fc_isSelect ?
					 errmsg("SELECT rule's target entry %d has different size from column \"%s\"",
							fc_i, fc_attname) :
					 errmsg("RETURNING list's entry %d has different size from column \"%s\"",
							fc_i, fc_attname),
					 fc_isSelect ?
					 errdetail("SELECT target entry has type %s, but column has type %s.",
							   format_type_with_typemod(fc_tletypid, fc_tletypmod),
							   format_type_with_typemod(fc_attr->atttypid,
														fc_attr->atttypmod)) :
					 errdetail("RETURNING list entry has type %s, but column has type %s.",
							   format_type_with_typemod(fc_tletypid, fc_tletypmod),
							   format_type_with_typemod(fc_attr->atttypid,
														fc_attr->atttypmod))));
	}

	if (fc_i != fc_resultDesc->natts)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 fc_isSelect ?
				 errmsg("SELECT rule's target list has too few entries") :
				 errmsg("RETURNING list has too few entries")));
}

/*
 * setRuleCheckAsUser
 *		递归扫描查询或表达式树，并将checkAsUser字段
 *		设置为所有rtable条目的给定用户ID。
 *
 * 注意：对于视图（ON SELECT规则），OLD
 * RTE条目的checkAsUser字段将在展开视图规则时被覆盖，
 * 而NEW条目的checkAsUser字段则不相关，因为该条目的
 * requiredPerms位将始终为零。 但是，对于其他类型的规则
 * 来说，将这些字段设置为匹配规则所有者是很重要的。 所以我们始终设置
 * 它们。
 */
void setRuleCheckAsUser(Node *fc_node, Oid fc_userid)
{
	(void) fc_setRuleCheckAsUser_walker(fc_node, &fc_userid);
}

static bool fc_setRuleCheckAsUser_walker(Node *fc_node, Oid *fc_context)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, Query))
	{
		fc_setRuleCheckAsUser_Query((Query *) fc_node, *fc_context);
		return false;
	}
	return expression_tree_walker(fc_node, fc_setRuleCheckAsUser_walker,
								  (void *) fc_context);
}

static void fc_setRuleCheckAsUser_Query(Query *fc_qry, Oid fc_userid)
{
	ListCell   *fc_l;

	/* 设置此查询节点中的所有RTE */
	foreach(fc_l, fc_qry->rtable)
	{
		RangeTblEntry *fc_rte = (RangeTblEntry *) lfirst(fc_l);

		if (fc_rte->rtekind == RTE_SUBQUERY)
		{
			/* 递归进入FROM中的子查询 */
			fc_setRuleCheckAsUser_Query(fc_rte->subquery, fc_userid);
		}
		else
			fc_rte->checkAsUser = fc_userid;
	}

	/* 递归进入WITH中的子查询 */
	foreach(fc_l, fc_qry->cteList)
	{
		CommonTableExpr *fc_cte = (CommonTableExpr *) lfirst(fc_l);

		fc_setRuleCheckAsUser_Query(castNode(Query, fc_cte->ctequery), fc_userid);
	}

	/* 如果有子链接，搜索它们并处理它们的RTE */
	if (fc_qry->hasSubLinks)
		query_tree_walker(fc_qry, fc_setRuleCheckAsUser_walker, (void *) &fc_userid,
						  QTW_IGNORE_RC_SUBQUERIES);
}


/*
 * 更改现有规则的触发语义。
 */
void EnableDisableRule(Relation fc_rel, const char *fc_rulename,
				  char fc_fires_when)
{
	Relation	pg_rewrite_desc;
	Oid			fc_owningRel = RelationGetRelid(fc_rel);
	Oid			fc_eventRelationOid;
	HeapTuple	fc_ruletup;
	Form_pg_rewrite fc_ruleform;
	bool		fc_changed = false;

	/*
	 * 查找要更改的规则元组。
	 */
	pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);
	fc_ruletup = SearchSysCacheCopy2(RULERELNAME,
								  ObjectIdGetDatum(fc_owningRel),
								  PointerGetDatum(fc_rulename));
	if (!HeapTupleIsValid(fc_ruletup))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("rule \"%s\" for relation \"%s\" does not exist",
						fc_rulename, get_rel_name(fc_owningRel))));

	fc_ruleform = (Form_pg_rewrite) GETSTRUCT(fc_ruletup);

	/*
	 * 验证用户是否具有适当的权限。
	 */
	fc_eventRelationOid = fc_ruleform->ev_class;
	Assert(fc_eventRelationOid == fc_owningRel);
	if (!pg_class_ownercheck(fc_eventRelationOid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(fc_eventRelationOid)),
					   get_rel_name(fc_eventRelationOid));

	/*
	 * 如果与所需的新状态不同，则更改ev_enabled。
	 */
	if (DatumGetChar(fc_ruleform->ev_enabled) !=
		fc_fires_when)
	{
		fc_ruleform->ev_enabled = CharGetDatum(fc_fires_when);
		CatalogTupleUpdate(pg_rewrite_desc, &fc_ruletup->t_self, fc_ruletup);

		fc_changed = true;
	}

	InvokeObjectPostAlterHook(RewriteRelationId, fc_ruleform->oid, 0);

	heap_freetuple(fc_ruletup);
	table_close(pg_rewrite_desc, RowExclusiveLock);

	/*
	 * 如果我们更改了任何内容，广播 SI inval 消息以强制每个
	 * 后端（包括我们自己！）重建关系的 relcache 条目。
	 * 否则，它们将无法及时应用更改。
	 */
	if (fc_changed)
		CacheInvalidateRelcache(fc_rel);
}


/*
 * 在获取关系锁定之前执行权限和完整性检查。
 */
static void fc_RangeVarCallbackForRenameRule(const RangeVar *fc_rv, Oid fc_relid, Oid fc_oldrelid,
							  void *fc_arg)
{
	HeapTuple	fc_tuple;
	Form_pg_class fc_form;

	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tuple))
		return;					/* 并发删除 */
	fc_form = (Form_pg_class) GETSTRUCT(fc_tuple);

	/* 只有表和视图可以有规则 */
	if (fc_form->relkind != RELKIND_RELATION &&
		fc_form->relkind != RELKIND_VIEW &&
		fc_form->relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("relation \"%s\" cannot have rules", fc_rv->relname),
				 errdetail_relkind_not_supported(fc_form->relkind)));

	if (!allowSystemTableMods && IsSystemClass(fc_relid, fc_form))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						fc_rv->relname)));

	/* 你必须拥有该表才能重命名其中的规则 */
	if (!pg_class_ownercheck(fc_relid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(fc_relid)), fc_rv->relname);

	ReleaseSysCache(fc_tuple);
}

/*
 * 重命名现有的重写规则。
 */
ObjectAddress RenameRewriteRule(RangeVar *fc_relation, const char *fc_oldName,
				  const char *fc_newName)
{
	Oid			fc_relid;
	Relation	fc_targetrel;
	Relation	pg_rewrite_desc;
	HeapTuple	fc_ruletup;
	Form_pg_rewrite fc_ruleform;
	Oid			fc_ruleOid;
	ObjectAddress fc_address;

	/*
	 * 查找名称，检查权限，并获取锁（我们不会在事务结束之前释放这个锁）。
	 */
	fc_relid = RangeVarGetRelidExtended(fc_relation, AccessExclusiveLock,
									 0,
									 fc_RangeVarCallbackForRenameRule,
									 NULL);

	/* 已经持有锁，因此只需构建 relcache 条目。 */
	fc_targetrel = relation_open(fc_relid, NoLock);

	/* 准备修改pg_rewrite */
	pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);

	/* 获取规则的条目（它最好存在） */
	fc_ruletup = SearchSysCacheCopy2(RULERELNAME,
								  ObjectIdGetDatum(fc_relid),
								  PointerGetDatum(fc_oldName));
	if (!HeapTupleIsValid(fc_ruletup))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("rule \"%s\" for relation \"%s\" does not exist",
						fc_oldName, RelationGetRelationName(fc_targetrel))));
	fc_ruleform = (Form_pg_rewrite) GETSTRUCT(fc_ruletup);
	fc_ruleOid = fc_ruleform->oid;

	/* 具有新名称的规则不应已经存在 */
	if (IsDefinedRewriteRule(fc_relid, fc_newName))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("rule \"%s\" for relation \"%s\" already exists",
						fc_newName, RelationGetRelationName(fc_targetrel))));

	/*
	 * 我们不允许重命名ON SELECT规则，因为它们应该始终
	 * 命名为"_RETURN"。
	 */
	if (fc_ruleform->ev_type == CMD_SELECT + '0')
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("renaming an ON SELECT rule is not allowed")));

	/* 好的，进行更新 */
	namestrcpy(&(fc_ruleform->rulename), fc_newName);

	CatalogTupleUpdate(pg_rewrite_desc, &fc_ruletup->t_self, fc_ruletup);

	InvokeObjectPostAlterHook(RewriteRelationId, fc_ruleOid, 0);

	heap_freetuple(fc_ruletup);
	table_close(pg_rewrite_desc, RowExclusiveLock);

	/*
	 * 使关系的 relcache 条目失效，以便其他后端（以及这个后端！）收到 SI 消息以重新构建 relcache 条目。
	 * （理想情况下，这应该自动发生...）
	 */
	CacheInvalidateRelcache(fc_targetrel);

	ObjectAddressSet(fc_address, RewriteRelationId, fc_ruleOid);

	/*
	 * 关闭 rel，但保持独占锁定！
	 */
	relation_close(fc_targetrel, NoLock);

	return fc_address;
}
